#! /usr/bin/env python
# -*- coding: UTF-8 -*-

from __future__ import with_statement

# System packages

import sys
import os
import webbrowser

    #
    # I wonder where does gettext look for translation by default.
    # If I knew this, I wouldn't hardcode /usr/share/locale here.
    #
import gettext
gettext.install('habr-monitor-applet', '/usr/share/locale', unicode=True)

# GTK packages

import pygtk
pygtk.require('2.0')

import gtk
gtk.gdk.threads_init()

import gnomeapplet

# Our own packages

from gconf_client import GConfClient
from network_manager import NetworkManagerObservee
from settings_window import SettingsWindow
from transparent_label import TransparentLabel
from url_thread import UrlThread


__license__ = 'GNU General Public License v.2'
__version__ = '0.1.5'
__author__ = _('Eugene Arshinov <earshinov@gmail.com>')


#
# A note about GConf.
#
# I follow the recomended way of using GConf, described
# within examples in python-gconf documentation. They can
# be found at /usr/share/doc/python-gconf/examples/.
#
# This way is also used in many apps, for example in
# the Computer Temperature Monitor applet
# (see http://computertemp.berlios.de/).
#
# We have the main module (i.e., our applet), the
# settings window and the GConf database.
#
# --------------------
#
# When the main module is started, it
#
# 1. read settings from the GConf database directly
#
# 2. set up GConf notification callbacks on our
#    configuration GConf entries. The callbacks will be
#    called whenever a configuration entry is changed.
#
# It may be possible to use for the items above the same
# code that applies GConf settings to the main module.
# I do it, though some other apps do not (e.g., the
# Computer Temperature Monitor applet mentioned above).
#
# --------------------
#
# When a user choose the "Preferences" menu item, our
# settings dialog is displayed. Evety time it
# initializes itself directly from the GConf database.
#
# When the user change a setting in the dialog, the
# settings window saves it directly to the database.
# The corresponding GConf notification callback is fired
# and the main module applies the new settings.
#
# Is may be possible to avoid direct initialization from
# GConf by using the GConf notification callbacks
# mentioned above. But apps I know about use the
# direct initialization, so do I as I consider
# less code to be written in this case.
#


class HabrahabrMonitorApplet(gnomeapplet.Applet):
    __PADDING = 3
    __INACTIVE_TRANSPARENCY=0.6

    # Main applet window

    def __init__(self, applet, iid):
            #
            # This is a transparency hack according to
            # http://gnome-applets.sourcearchive.com/documentation/2.24.1/gswitchit-applet_8c-source.html
            #
            # I did not find any documentation for this method.
            #
        applet.set_background_widget(applet)

        self.applet = applet

        self.__gconf = GConfClient(self.applet.get_preferences_key())

        # ---

        self.karmaLabel = TransparentLabel('?')
        self.habrapowerLabel = TransparentLabel('?')
        self.ratingLabel = TransparentLabel('(?)')

        def karmaTextColorChanged(setting):
            color = setting.value()
            self.karmaLabel.set_rgb(
                color.red   / 65536.0,
                color.green / 65536.0,
                color.blue  / 65536.0)
        self.__gconf.karmaTextColor.attach(karmaTextColorChanged)

        def habrapowerTextColorChanged(setting):
            color = setting.value()
            self.habrapowerLabel.set_rgb(
                color.red   / 65536.0,
                color.green / 65536.0,
                color.blue  / 65536.0)
        self.__gconf.habrapowerTextColor.attach(habrapowerTextColorChanged)

        def ratingTextColorChanged(setting):
            color = setting.value()
            self.ratingLabel.set_rgb(
                color.red   / 65536.0,
                color.green / 65536.0,
                color.blue  / 65536.0)
        self.__gconf.ratingTextColor.attach(ratingTextColorChanged)

        def karmaFontChanged(setting):
            font = setting.font()
            self.karmaLabel.modify_font(font)
            self.habrapowerLabel.modify_font(font)
        self.__gconf.karmaFont.attach(karmaFontChanged)

        def ratingFontChanged(setting):
            self.ratingLabel.modify_font(setting.font())
        self.__gconf.ratingFont.attach(ratingFontChanged)

        # ---

        self.box = None # gtk.HBox or a gtk.Vbox to store the labels
        self.__construct_widgets()

        self.__gconf.layoutInRow.attach(lambda setting: self.__rearrange())
        self.__gconf.textIsAlwaysHorizontal.attach(lambda setting: self.__rearrange())

        def showRatingChanged(setting):
            self.ratingLabel.set_property('visible', setting.value())
            self.ratingLabel.set_no_show_all(True)
        self.__gconf.showRating.attach(showRatingChanged)

        # ---

        self.__error = None

        self.__nm_observee = NetworkManagerObservee()
        def nm_observer(nm_observee):
            #
            # There is an idea to disable Refresh item of the popup menu
            # if network is not available. BUT I think we should not forbid
            # updates so strictly and should enable users to do what they want.
            #
            if nm_observee.connected:
                if self.__error is not None:
                    self._failure()
                else:
                    self._connected()
            else:
                self._disconnected()
        self.__nm_observee.attach(nm_observer)

        # ---

        self.__thread = UrlThread(self.__gconf, self.__nm_observee)
        class AttemptObserver(UrlThread.AttemptObserver):
            def success(this, thread, karma, habrapower, rating):
                self._connected((karma, habrapower, rating))
            def failure(this, thread, error):
                self._failure(error, thread.nick_changed)
        self.__thread.attach(AttemptObserver())

        # ---

        self.__create_menu()

        self.applet.connect('destroy', self.__cb_destroy)
        self.applet.show_all()

    # State

    def _connected(self, data=None):
        self.__error = None

        self.karmaLabel.set_alpha(1)
        self.habrapowerLabel.set_alpha(1)
        self.ratingLabel.set_alpha(1)

        if data is not None:
            karma, habrapower, rating = data
            self.karmaLabel.set_text(karma)
            self.habrapowerLabel.set_text(habrapower)
            self.ratingLabel.set_text('(%s)' % rating)

        self.karmaLabel.set_tooltip_text(_('Karma'))
        self.habrapowerLabel.set_tooltip_text(_('Habrapower'))
        self.ratingLabel.set_tooltip_text(_('Rank'))

    def _failure(self, error=None, nick_changed=False):
        assert error is not None or self.__error is not None
        if error is not None:
            self.__error = error
        else:
            error = self.__error

        self.karmaLabel.set_alpha(self.__INACTIVE_TRANSPARENCY)
        self.habrapowerLabel.set_alpha(self.__INACTIVE_TRANSPARENCY)
        self.ratingLabel.set_alpha(self.__INACTIVE_TRANSPARENCY)

        if nick_changed:
            self.karmaLabel.set_text('?')
            self.habrapowerLabel.set_text('?')
            self.ratingLabel.set_text('(?)')

        self.karmaLabel.set_tooltip_text('%s %s' % (_('Last value of karma.'), error))
        self.habrapowerLabel.set_tooltip_text('%s %s' % (_('Last value of habrapower.'), error))
        self.ratingLabel.set_tooltip_text('%s %s' % (_('Last value of rank.'), error))

    def _disconnected(self):
        self.karmaLabel.set_alpha(self.__INACTIVE_TRANSPARENCY)
        self.habrapowerLabel.set_alpha(self.__INACTIVE_TRANSPARENCY)
        self.ratingLabel.set_alpha(self.__INACTIVE_TRANSPARENCY)

        error = _('Please connect to the network to be able to update.')
        self.karmaLabel.set_tooltip_text('%s %s' % (_('Last value of karma.'), error))
        self.habrapowerLabel.set_tooltip_text('%s %s' % (_('Last value of habrapower.'), error))
        self.ratingLabel.set_tooltip_text('%s %s' % (_('Last value of rank.'), error))

    # Helper methods

    def __rearrange(self):

        orient = self.applet.get_orient()
        assert orient == gnomeapplet.ORIENT_UP \
            or orient == gnomeapplet.ORIENT_DOWN \
            or orient == gnomeapplet.ORIENT_LEFT \
            or orient == gnomeapplet.ORIENT_RIGHT

        if self.box is not None:
            self.applet.remove(self.box)

        vertical = (orient == gnomeapplet.ORIENT_LEFT or orient == gnomeapplet.ORIENT_RIGHT)
        if not vertical and self.__gconf.layoutInRow.value() or \
           vertical and not (self.__gconf.layoutInRow.value() ^ self.__gconf.textIsAlwaysHorizontal.value()):
            self.box = gtk.HBox()
        else:
            self.box = gtk.VBox()

        if orient == gnomeapplet.ORIENT_RIGHT and \
           self.__gconf.layoutInRow.value() and \
           not self.__gconf.textIsAlwaysHorizontal.value():
            self.box.pack_end(self.karmaLabel, False, False, self.__PADDING)
            self.box.pack_end(self.habrapowerLabel, False, False, self.__PADDING)
            self.box.pack_end(self.ratingLabel, False, False, self.__PADDING)
        else:
            self.box.pack_start(self.karmaLabel, False, False, self.__PADDING)
            self.box.pack_start(self.habrapowerLabel, False, False, self.__PADDING)
            self.box.pack_start(self.ratingLabel, False, False, self.__PADDING)

        pos = gtk.POS_TOP
        if not self.__gconf.textIsAlwaysHorizontal.value():
            if orient == gnomeapplet.ORIENT_LEFT:
                pos = gtk.POS_LEFT
            elif orient == gnomeapplet.ORIENT_RIGHT:
                pos = gtk.POS_RIGHT
        self.karmaLabel.set_pos(pos)
        self.habrapowerLabel.set_pos(pos)
        self.ratingLabel.set_pos(pos)

        self.box.show_all()
        self.ratingLabel.set_property('visible', self.__gconf.showRating.value())

        self.applet.add(self.box)

    def __create_menu(self):
        menu_xml = '''
            <popup name='button3'>
                <menuitem name='Refresh Item'     verb='Refresh'     stockid='gtk-refresh'/>
                <menuitem name='Preferences Item' verb='Preferences' stockid='gtk-properties'/>
                <menuitem name='About Item'       verb='About'       stockid='gtk-about'/>
            </popup>'''
        menu_verbs = [
            ('Refresh',     self.__cb_refresh),
            ('Preferences', self.__cb_preferences),
            ('About',       self.__cb_about)]
        self.applet.setup_menu(menu_xml, menu_verbs, None)

    def __construct_widgets(self):
        self.__rearrange()
        self.applet.connect('change-orient', lambda applet, orient: self.__rearrange())

    # Callbacks

    def __cb_refresh(self, event, data=None):
        self.__thread.update()

    def __cb_about(self, event, data=None):
        dlg = gtk.AboutDialog()
        dlg.set_icon_from_file('/usr/share/gnome-panel/pixmaps/habr-monitor-applet.png')
        dlg.set_logo(gtk.gdk.pixbuf_new_from_file('/usr/share/gnome-panel/pixmaps/habr-monitor-applet.png'))

        dlg.set_name('Habrahabr Monitor Applet')
        dlg.set_version(__version__)
        dlg.set_comments(_('Displays your karma and habrapower.'))

        dlg.set_copyright('© 2009 Eugene Arshinov')
        dlg.set_website('http://habr-monitor-applet.googlecode.com/')
        dlg.set_website_label('habr-monitor-applet.googlecode.com')
        def show_website(dlg, link):
            webbrowser.open(link)
        gtk.about_dialog_set_url_hook(show_website)

        dlg.set_authors((__author__,))
        dlg.set_license(__license__)

        dlg.run()
        dlg.destroy()

    def __cb_preferences(self, event, data=None):
        SettingsWindow(self.__gconf, self.__nm_observee)

    def __cb_destroy(self, applet):
        self.__thread.stop()
        del self.applet

# Main function

def applet_factory(applet, iid):
    HabrahabrMonitorApplet(applet, iid)
    return True

def main(args):
    #
    # Some people prefer running the applet in its own window
    # when debugging. This way is described in Russian at
    # [ http://www.opennet.ru/base/dev/gnome_applet_pygtk.txt.html ].
    #
    # I use the way described at
    # [ http://www.davyd.id.au/articles/debugging-gnome-applets.shtml ]
    # instead.
    #
    # Due to some differences between C and Python applet
    # programming, the sequence of actions looks as follows:
    #
    # * Run the applet using your favourite shell either from your
    #   source directory or from /usr/lib/gnome-panel/..., it does not matter
    #
    # * Add the applet to a panel. This will attach to your
    #   running process and you will be able to see all warnings,
    #   exception information etc.
    #
    # * DO NOT EVER REMOVE THE APPLET YOUR ARE DEBUGGING FROM THE PANEL DIRECTLY.
    #   If you've done it, you will need to `killall python` or to log out
    #   and log in again.
    #
    # * After you've changed the source, kill the running process
    #   (I assume you use `sh` or `bash` shell):
    #
    #   * Press Ctrl-Z
    #
    #   * Type `jobs -p` and look for the applet's PID
    #
    #   * Type `kill -KILL <PID>` replacing <PID> with the applet's PID
    #
    # * The panel will show message about your applet crash and ask
    #   whether to reload it. Do not press anything.
    #
    # * Re-run your applet
    #
    # * Now press 'Reload' in the message box mensioned above.
    #
    gnomeapplet.bonobo_factory(
        'OAFIID:GNOME_HabrMonitorApplet_Factory',
        HabrahabrMonitorApplet.__gtype__,
        'Habrahabr Monitor Applet',
        '0',
        applet_factory)

if __name__ == '__main__':
    main(sys.argv)
